/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// #define LOG_NDEBUG 0
#define LOG_TAG "WorkQueue"

#include <utils/Log.h>
#include "WorkQueue.h"

namespace android {

// --- WorkQueue ---

    WorkQueue::WorkQueue(size_t maxThreads, bool canCallJava) :
            mMaxThreads(maxThreads), mCanCallJava(canCallJava),
            mCanceled(false), mFinished(false), mIdleThreads(0) {
    }

    WorkQueue::~WorkQueue() {
        if (!cancel()) {
            finish();
        }
    }

    status_t WorkQueue::schedule(WorkUnit *workUnit, size_t backlog) {
        AutoMutex _l(mLock);

        if (mFinished || mCanceled) {
            return INVALID_OPERATION;
        }

        if (mWorkThreads.size() < mMaxThreads
            && mIdleThreads < mWorkUnits.size() + 1) {
            sp<WorkThread> workThread = new WorkThread(this, mCanCallJava);
            status_t status = workThread->run("WorkQueue::WorkThread");
            if (status) {
                return status;
            }
            mWorkThreads.add(workThread);
            mIdleThreads += 1;
        } else if (backlog) {
            while (mWorkUnits.size() >= mMaxThreads * backlog) {
                mWorkDequeuedCondition.wait(mLock);
                if (mFinished || mCanceled) {
                    return INVALID_OPERATION;
                }
            }
        }

        mWorkUnits.add(workUnit);
        mWorkChangedCondition.broadcast();
        return OK;
    }

    status_t WorkQueue::cancel() {
        AutoMutex _l(mLock);

        return cancelLocked();
    }

    status_t WorkQueue::cancelLocked() {
        if (mFinished) {
            return INVALID_OPERATION;
        }

        if (!mCanceled) {
            mCanceled = true;

            size_t count = mWorkUnits.size();
            for (size_t i = 0; i < count; i++) {
                delete mWorkUnits.itemAt(i);
            }
            mWorkUnits.clear();
            mWorkChangedCondition.broadcast();
            mWorkDequeuedCondition.broadcast();
        }
        return OK;
    }

    status_t WorkQueue::finish() {
        { // acquire lock
            AutoMutex _l(mLock);

            if (mFinished) {
                return INVALID_OPERATION;
            }

            mFinished = true;
            mWorkChangedCondition.broadcast();
        } // release lock

        // It is not possible for the list of work threads to change once the mFinished
        // flag has been set, so we can access mWorkThreads outside of the lock here.
        size_t count = mWorkThreads.size();
        for (size_t i = 0; i < count; i++) {
            mWorkThreads.itemAt(i)->join();
        }
        mWorkThreads.clear();
        return OK;
    }

    bool WorkQueue::threadLoop() {
        WorkUnit *workUnit;
        { // acquire lock
            AutoMutex _l(mLock);

            for (;;) {
                if (mCanceled) {
                    return false;
                }

                if (!mWorkUnits.isEmpty()) {
                    workUnit = mWorkUnits.itemAt(0);
                    mWorkUnits.removeAt(0);
                    mIdleThreads -= 1;
                    mWorkDequeuedCondition.broadcast();
                    break;
                }

                if (mFinished) {
                    return false;
                }

                mWorkChangedCondition.wait(mLock);
            }
        } // release lock

        bool shouldContinue = workUnit->run();
        delete workUnit;

        { // acquire lock
            AutoMutex _l(mLock);

            mIdleThreads += 1;

            if (!shouldContinue) {
                cancelLocked();
                return false;
            }
        } // release lock

        return true;
    }

// --- WorkQueue::WorkThread ---

    WorkQueue::WorkThread::WorkThread(WorkQueue *workQueue, bool canCallJava) :
            Thread(canCallJava), mWorkQueue(workQueue) {
    }

    WorkQueue::WorkThread::~WorkThread() {
    }

    bool WorkQueue::WorkThread::threadLoop() {
        return mWorkQueue->threadLoop();
    }

};  // namespace android
